﻿using System;
using System.Globalization;
using System.Collections.Generic;

namespace Gmantis.Controls
{
    internal class Range<T> : IComparable<Range<T>>, IComparable<T>, IComparable where T : IComparable<T>, IConvertible
    {
        // Fields
        private readonly T _lowerBound;
        private readonly T _upperBound;

        // Methods
        internal Range(T lowerBound, T upperBound)
        {
            RangeAssert.NotNull("lowerBound", lowerBound);
            RangeAssert.NotNull("upperBound", upperBound);
            RangeAssert.IsTrue("lowerBound", lowerBound.CompareTo(upperBound) <= 0);
            this._lowerBound = lowerBound;
            this._upperBound = upperBound;
        }

        public int CompareTo(Range<T> other)
        {
            return this.LowerBound.CompareTo(other.LowerBound);
        }

        public int CompareTo(T other)
        {
            return this.LowerBound.CompareTo(other);
        }

        public int CompareTo(object obj)
        {
            if (obj is Range<T>)
            {
                Range<T> range = (Range<T>)obj;
                return this.CompareTo(range);
            }
            if (!(obj is T))
            {
                throw new InvalidOperationException(string.Format("Cannot compare to {0}", obj));
            }
            T other = (T)obj;
            return this.CompareTo(other);
        }

        public Range<T> Complement(Range<T> value)
        {
            T upperBound;
            RangeAssert.NotNull("value", value);
            RangeAssert.IsFalse("value", this.Contains(value));
            if (!this.Overlaps(value))
            {
                return (Range<T>)this;
            }
            if ((this.LowerBound.CompareTo(value.LowerBound) > 0) && (this.LowerBound.CompareTo(value.UpperBound) < 0))
            {
                upperBound = value.UpperBound;
            }
            else
            {
                upperBound = this.LowerBound;
            }
            if ((this.UpperBound.CompareTo(value.LowerBound) > 0) && (this.UpperBound.CompareTo(value.UpperBound) < 0))
            {
                return new Range<T>(upperBound, value.LowerBound);
            }
            return new Range<T>(upperBound, this.UpperBound);
        }

        public bool Contains(T value)
        {
            RangeAssert.NotNull("value", value);
            return ((this.LowerBound.CompareTo(value) <= 0) && (this.UpperBound.CompareTo(value) >= 0));
        }

        public bool Contains(Range<T> value)
        {
            RangeAssert.NotNull("value", value);
            this.LowerBound.CompareTo(value.LowerBound);
            this.UpperBound.CompareTo(value.UpperBound);
            return ((this.LowerBound.CompareTo(value.LowerBound) <= 0) && (this.UpperBound.CompareTo(value.UpperBound) >= 0));
        }

        public override bool Equals(object obj)
        {
            if (!(obj is Range<T>))
            {
                return false;
            }
            Range<T> other = (Range<T>)obj;
            return ((this.CompareTo(other) == 0) && (this.UpperBound.CompareTo(other.UpperBound) == 0));
        }

        public override int GetHashCode()
        {
            return this.LowerBound.GetHashCode();
        }

        public Range<T> Intersect(Range<T> value)
        {
            T lowerBound;
            RangeAssert.NotNull("value", value);
            RangeAssert.IsTrue("value", this.Overlaps(value));
            if (this.LowerBound.CompareTo(value.LowerBound) > 0)
            {
                lowerBound = this.LowerBound;
            }
            else
            {
                lowerBound = value.LowerBound;
            }
            if (this.UpperBound.CompareTo(value.UpperBound) < 0)
            {
                return new Range<T>(lowerBound, this.UpperBound);
            }
            return new Range<T>(lowerBound, value.UpperBound);
        }

        public bool IsContainedBy(Range<T> value)
        {
            RangeAssert.NotNull("value", value);
            return value.Contains((Range<T>)this);
        }

        public bool IsContiguousWith(Range<T> range)
        {
            if (((!this.Overlaps(range) && !range.Overlaps((Range<T>)this)) && (!range.Contains((Range<T>)this) && !this.Contains(range))) && !this.UpperBound.Equals(range.LowerBound))
            {
                return this.LowerBound.Equals(range.UpperBound);
            }
            return true;
        }

        public bool IsJoinableWith(Range<T> value, Func<T, T> incrementor)
        {
            if (!incrementor(this.UpperBound).Equals(value.LowerBound))
            {
                return incrementor(value.UpperBound).Equals(this.LowerBound);
            }
            return true;
        }

        public IEnumerable<T> Iterate(Func<T, T> incrementor)
        {
            yield return this.LowerBound;
            T other = incrementor(this.LowerBound);
            while (true)
            {
                if (this.UpperBound.CompareTo(other) < 0)
                {
                    yield break;
                }
                yield return other;
                other = incrementor(other);
            }
        }

        public Range<T> Join(Range<T> value, Func<T, T> incrementor)
        {
            RangeAssert.NotNull("value", value);
            RangeAssert.IsTrue("value", this.IsJoinableWith(value, incrementor));
            if (incrementor(this.UpperBound).Equals(value.LowerBound))
            {
                return new Range<T>(this.LowerBound, value.UpperBound);
            }
            return new Range<T>(value.LowerBound, this.UpperBound);
        }

        public static Range<T> operator &(Range<T> left, Range<T> right)
        {
            return left.Intersect(right);
        }

        public static Range<T> operator |(Range<T> left, Range<T> right)
        {
            return left.Union(right);
        }

        public static bool operator ==(Range<T> left, Range<T> right)
        {
            return (object.ReferenceEquals(left, right) || (((!object.ReferenceEquals(left, null)) && (!object.ReferenceEquals(right, null))) && (left.CompareTo(right) == 0)));
        }

        public static Range<T> operator ^(Range<T> left, Range<T> right)
        {
            return left.Complement(right);
        }

        public static bool operator >(Range<T> left, Range<T> right)
        {
            return (left.CompareTo(right) > 0);
        }

        public static bool operator >(Range<T> left, T right)
        {
            return (left.CompareTo(right) > 0);
        }

        public static bool operator >=(Range<T> left, Range<T> right)
        {
            return (left.CompareTo(right) >= 0);
        }

        public static bool operator >=(Range<T> left, T right)
        {
            return (left.CompareTo(right) >= 0);
        }

        public static bool operator !=(Range<T> left, Range<T> right)
        {
            return !(left == right);
        }

        public static bool operator <(Range<T> left, Range<T> right)
        {
            return (left.CompareTo(right) < 0);
        }

        public static bool operator <(Range<T> left, T right)
        {
            return (left.CompareTo(right) < 0);
        }

        public static bool operator <=(Range<T> left, Range<T> right)
        {
            return (left.CompareTo(right) <= 0);
        }

        public static bool operator <=(Range<T> left, T right)
        {
            return (left.CompareTo(right) <= 0);
        }

        public bool Overlaps(Range<T> value)
        {
            RangeAssert.NotNull("value", value);
            if ((!this.Contains(value.LowerBound) && !this.Contains(value.UpperBound)) && !value.Contains(this.LowerBound))
            {
                return value.Contains(this.UpperBound);
            }
            return true;
        }

        public IEnumerable<T> ReverseIterate(Func<T, T> decrementor)
        {
            yield return this.UpperBound;
            T other = decrementor(this.UpperBound);
            while (true)
            {
                if (this.CompareTo(other) > 0)
                {
                    yield break;
                }
                yield return other;
                other = decrementor(other);
            }
        }

        public IEnumerable<Range<T>> Split(T position)
        {
            RangeAssert.NotNull("position", position);
            RangeAssert.IsTrue("position", this.Contains(position));
            if ((this.LowerBound.CompareTo(position) != 0) && (this.UpperBound.CompareTo(position) != 0))
            {
                yield return new Range<T>(this.LowerBound, position);
                yield return new Range<T>(position, this.UpperBound);
            }
            else
            {
                yield return this;
            }
        }

        public IEnumerable<Range<T>> Substract(Range<T> value, Func<T, T> incrementor, Func<T, T> decrementor)
        {
            RangeAssert.NotNull("value", value);
            List<Range<T>> list = new List<Range<T>>();
            if (!this.Equals(value) && !value.Contains((Range<T>)this))
            {
                if (this.Overlaps(value))
                {
                    T upperBound = decrementor(value.LowerBound);
                    T lowerBound = incrementor(value.UpperBound);
                    if (this.LowerBound.CompareTo(value.LowerBound) < 0)
                    {
                        list.Add(new Range<T>(this.LowerBound, upperBound));
                    }
                    if (this.UpperBound.CompareTo(value.UpperBound) > 0)
                    {
                        list.Add(new Range<T>(lowerBound, this.UpperBound));
                    }
                    return list;
                }
                list.Add((Range<T>)this);
            }
            return list;
        }

        public Range<T> Union(Range<T> value)
        {
            T lowerBound;
            RangeAssert.NotNull("value", value);
            RangeAssert.IsTrue("value", this.IsContiguousWith(value));
            if (this.Contains(value))
            {
                return (Range<T>)this;
            }
            if (value.Contains((Range<T>)this))
            {
                return value;
            }
            if (this.LowerBound.CompareTo(value.LowerBound) < 0)
            {
                lowerBound = this.LowerBound;
            }
            else
            {
                lowerBound = value.LowerBound;
            }
            if (this.UpperBound.CompareTo(value.UpperBound) > 0)
            {
                return new Range<T>(lowerBound, this.UpperBound);
            }
            return new Range<T>(lowerBound, value.UpperBound);
        }

        public override string ToString()
        {
            return string.Concat(new object[] { "{", this.LowerBound, "->", this.UpperBound, "}" });
        }

        // Properties
        public int Length
        {
            get
            {
                return ((this._upperBound.ToInt32(CultureInfo.InvariantCulture.NumberFormat) - this._lowerBound.ToInt32(CultureInfo.InvariantCulture.NumberFormat)) + 1);
            }
        }

        public T LowerBound
        {
            get
            {
                return this._lowerBound;
            }
        }

        public T UpperBound
        {
            get
            {
                return this._upperBound;
            }
        }
    }
}
